<?php

class VC_Counter {
	
	private $id;
	
	public function __counstruct($pid = NULL) {
		if(isset($pid)) {
			$this->id = $pid;
		} else {
			global $id;
			$this->id = $id;
		}
	}
	
	private function countOld($data) {					//	Parses information from old-type database
		if(!is_array($data)) return NULL;
		
		$views 		= 0;
		$visitors = 0;
		foreach($data as $d) {
			$views += intval($d["views"]);
			$visitors++;
		}
		
		return array($views, $visitors);
	}
	
	private function countNew($data) {					//	Parses information from new-type database
		if(!isset($data)) return NULL;
		
		$d = explode('/', $data);
		if(count($d) < 2) return NULL;
		
		$views 		= hexdec(array_shift($d));
		$visitors = count($d);
		
		return array($views, $visitors);
	}
	
	public function getStats($id = NULL)	{			//	Processes information from database
		if(!isset($id)) {
			if(!isset($this->id)) return NULL;
			$id = $this->id;
		}
		
		VC_System::logThis(1, "getStats(): Getting data from DB");
		
		$dm			= new VC_DatabaseMaster();
		$stats	= $dm->getStatsData($id);
		
		VC_System::logThis(1, "getStats(): Got stats in raw format:\n" . print_r($stats, true));
		VC_System::logThis(1, "getStats(): Consts:\nNEW_TYPE_STATS=" . NEW_TYPE_STATS . "\nOLD_TYPE_STATS=" . OLD_TYPE_STATS);
		$retval = array();
		switch($stats[0]) {
			case NEW_TYPE_STATS:
				$retval = self::countNew($stats[1]);
				VC_System::logThis(1, "getStats(): Got data in new format:\n" . print_r($retval, true));
				break;
			default:
				$retval = self::countOld($stats[1]);
				VC_System::logThis(1, "getStats(): Got data in old format:\n" . print_r($retval, true));
		}
		
		return $retval;
	}
	
	public function increase($content=NULL)	{			//	Increases counter for current post
		VC_System::logThis(1, "increase(): Increasing counter");
		global $vc_param;
		
		VC_System::logThis(1, "increase(): Checking that it is the main page and we permitted to count it");
		if(
				!is_single &&																				//	If this is not the single post page
				$vc_param->assertEquals('count_main_page_views', 1)	//	and if we are not permitted to count main page views
			) return $content;
		
		VC_System::logThis(1, "increase(): Checking post ID");
		if( !isset($this->id) ) {								//	If inner variable is not set
			global $id;
			if( !isset($id) ) {
				VC_System::logThis(3, "increase(): Can't get ID of the post");
				return $content;		//	...and global variable, too
			}
			$this->id = $id;
		}
		
		VC_System::logThis(1, "increase(): Checking that we are permitted to count views and that the content exists");
		if(
				!$vc_param->assertEquals(display_views, "1") &&
				isset($content)
			) return $content;
		
		VC_System::logThis(1, "increase(): Checking that current user is the owner of the post");
		if(!current_user_can('edit_post', $id))
		{
			$ip = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : "0.0.0.0";
			$dm = new VC_DatabaseMaster();
			$dm->saveToDB($id, $ip);
		}
		
		VC_System::logThis(1, "increase(): Getting stats");
		
		$stats = self::getStats($this->id);
		
		VC_System::logThis(1, "increase(): Got stats in raw format:\n" . print_r($stats, true));
		
		$formatter = new VC_Format($stats[0], $stats[1]);
		$str = $formatter->get();
		
		VC_System::logThis(1, "increase(): Got the following string for the post: " . $str);
		
		VC_System::logThis(1, "increase(): Parsing command line");
		
		if(
				$vc_param->assertEquals(display_views, "1") &&
				isset($content)
			) {
				$commands = new VC_Commands();
				if($commands->assertEquals("-place", "top")) {
					return $str . $content;
				} else {
					return $content . $str;
				}
			} else {
				return $str;
			}
	}
}

class VC_Commands {
	private static $command_line;			//	Command line string
	private static $commands;					//	Array of commands
	
	public function __construct() {
		$this->command_line = NULL;
		$this->commands = NULL;
		self::refreshCL();
		self::parseCL();
	}
	
	private function parseCL() {			//	Parses command line into commands array
		if(!isset($this->command_line)) {
			self::refreshCL();
		}
		$tmp = explode(" ", $this->command_line);
		$this->commands = array();
		foreach($tmp as $command) {
			if(FALSE === strpos($command, "=")) continue;	//	If user passed wrong command
			
			$tmp_2 = explode("=", $command);
			$this->commands[$tmp_2[0]] = $tmp_2[1];
		}
	}
	
	private function refreshCL() {		//	Refreshes command line (private)
		$this->command_line = get_settings('vc_command_line');
	}
	
	private function saveCL() {				//	Saves changed commands to database
		$tmp = array();
		foreach($this->commands as $key => $value) {
			array_push($tmp, $key . "=" . $value);
		}
		$this->command_line = implode(" ", $tmp);
		update_option('vc_command_line', $this->command_line);
	}
	
	public function refresh() {				//	Refreshes both command line and array of params
		self::refreshCL();
		self::parseCL();
	}
	
	public function get($command_name) {	//	Permits to get command value
		if(!is_array($this->commands)) self::parseCL();
		if(isset($this->commands[$command_name])) {
			return $this->commands[$command_name];
		}
		return FALSE;
	}
	
	public function set($command_name, $command_value) {	//	Permits to set command value
		if(!is_array($this->commands)) self::parseCL();
		$this->commands[$command_name] = $command_value;
		self::saveCL();
	}
	
	public function del($command_name) {
		if(!is_array($this->commands)) self::parseCL();
		if(array_key_exists($command_name, $this->commands)) {
			$this->commands = array_diff(
																		$this->commands,
																		array(
																					$command_name => $this->commands[$command_name]
																				 )
																	);
			self::saveCL();
			return TRUE;
		}
		return FALSE;
	}
	
	public function assertEquals($command_name, $value) {	//	Permits to compare value of command with another one
		if(!is_array($this->commands)) self::parseCL();
		if(!isset($this->commands[$command_name])) return FALSE;
		if($value != $this->commands[$command_name]) return FALSE;
		return TRUE;
	}
	
}

class VC_Format {
	private $views;
	private $visitors;
	private $output;
	
	public function __construct($views = 0, $visitors = 0) {
		$this->views		= $views;
		$this->visitors	= $visitors;
		$this->output		= NULL;
	}
	
	private function genString() {				// Permits to generate output string basing on type of ouput
		global $vc_param;
		
		if(!isset($this->views) || !isset($this->visitors)) {
			$output = NULL;
			return FALSE;
		}
		
		$tmp	= array();
		if($vc_param->assertEquals('display_views', 1)) {
			$tmp = self::genSimpleString();
		} else {
			$tmp = self::genExtString();
		}
		
		$this->output = self::purgeArray($tmp);
	}
	
	private function genSimpleString() {	//	Generates the string in a simple way
		global $vc_param;
		$retval	= array(
											 "<" . $vc_param->get("tag", "p") . ">",
											($vc_param->assertEquals("display_icon", "1") ? vc_get_icon() : NULL),
											 $vc_param->get("text_before", "Visited"),
											 $this->views,
											 $vc_param->get("human_count_text_" . (1 == $this->views ? "1" : "many"), "times"),
											 $vc_param->get("preposition"),
											 $this->visitors,
											 $vc_param->get("human_viewers_text_" . (1 == $this->visitors ? "1" : "many"), "visitors"),
											 "</" . $vc_param->get("tag", "p") . ">"
										);
		return $retval;
	}
	
	private function genExtString() {			//	Generates the string in the extended way
		global $vc_options, $vc_param;
		
		$human_ct_func = $vc_options->get('human_ct_function');
		$human_ct_text = "";
		if(isset($human_ct_func))
		{
			eval('$human_ct_text = ' . $human_ct_func . '(' . $this->views . ');');
		}
		else
		{
			$human_ct = $vc_options->get('human_ct');
			$human_ct_text = isset($human_ct) ? $human_ct :
														( $vc_param->get("human_count_text_" . (1 == $this->views ? "1" : "many"), "times") );
		}
		
		$human_vt_func = $vc_options->get('human_vt_function');
		$human_vt_text = "";
		if(isset($human_vt_func))
		{
			eval('$human_vt_text = ' . $human_vt_func . '(' . $this->visitors . ');');
		}
		else
		{
			$human_vt = $vc_options->get('human_vt');
			$human_vt_text = isset($human_vt) ? $human_vt :
														( $vc_param->get("human_viewers_text" . (1 == $this->visitors ? "1" : "many"), "visitors") );
		}
		
		$retval = array(
											($vc_options->assertEquals("with_icon", 1) ? vc_get_icon() : NULL),
											 $vc_options->get(
											 									"text_before",
											 									$vc_param->get("text_before", "Visited")
											 									),
											 $this->views,
											 $human_ct_text,
											 $vc_options->get("preposition"),
											 $this->visitors,
											 $human_vt_text
										);
		return $retval;
	}
	
	private function purgeArray($arr) {		//	Eliminates empty elements and implodes array using a space
		$tmp = implode(
										" ",
										array_diff(
																$arr,
																array(NULL)
															)
									);
		return $tmp;
	}

	public function get() {								// Permits to get generated string
		if(!isset($this->output)) self::genString();
		return $this->output;
	}
	
	public function ipToHEX($ip) {
		$tmp		= explode(".", $ip);
		$hexip	= array();
		foreach($tmp as $t) {
			array_push($hexip, sprintf("%02X", intval($t)));
		}
		
		return implode($hexip);
	}
}

class VC_ParamMaster {
	private $params;
	private $type;
	
	public function __construct($type = 0) {
		if(0 == $type) {								//	Type 0 means that we use ParamMaster to load data from DB
			self::loadParams();						//	Type 1 means that we use ParamMaster to store other data
		}
		
		$this->type = $type;
	}
	
	public function loadParams() {		//	Loads parameters from database
		global $wpdb, $table_prefix;
		
		$query = "SELECT option_name, option_value FROM " . $table_prefix . "options WHERE (option_name LIKE 'vc_%')";
		$params = $wpdb->get_results($query);
		
		$this->params = array();
		foreach($params as $param) {
			if("vc_" != substr($param->option_name, 0, 3)) continue;
			$pn = substr($param->option_name, 3);	//	Cutting "vc_" from the beginning of the option
			$this->params[$pn] = $param->option_value;
		}
	}
	
	public function get($param_name, $expected = NULL) {		//	Retrieves param value if exist or returns $expected value
		if(!array_key_exists(strtolower($param_name), $this->params)) return NULL;
		if(!isset($this->params[strtolower($param_name)])) {
			if(isset($expected)) return $expected;
			return NULL;
		}
		return $this->params[$param_name];
	}
	
	public function set($param_name, $value) {		//	Sets parameters values
		if(!isset($param_name)) return FALSE;
		if(!is_array($this->params)) {
			$this->params = array($param_name => $value);
		} else {
			$this->params[strtolower($param_name)] = $value; 
		}
		return TRUE;
	}
	
	public function assertEquals($param_name, $value) {			//	Checks whether passed value equals to the stored one
		if(!array_key_exists(strtolower($param_name), $this->params)) return NULL;
		if($value == $this->params[$param_name]) return TRUE;
		return FALSE;
	}
}

class VC_DatabaseMaster {
	
	public function __construct() {
		if(!defined("OLD_TYPE_STATS")) define("OLD_TYPE_STATS", 1);
		if(!defined("NEW_TYPE_STATS")) define("NEW_TYPE_STATS", 0);
	}
	
	private function getStatsOldData($id) {		//	Retrieves data from old-type database table
		global $wpdb, $table_prefix;
		$tableName = $table_prefix . "views_counter";
		
		if(!self::tableExists($tableName)) return NULL;
		
		$query = "SELECT * FROM {$tableName} WHERE (id = '{$id}')";
		return $wpdb->get_results($query, ARRAY_A);
	}

	private function getStatsNewData($id) {		//	Retrieves data from new-type database table
		global $wpdb, $table_prefix;
		$tableName = $table_prefix . "vc_data";

		if(!self::tableExists($tableName)) return NULL;
		
		$query = "SELECT data FROM {$tableName} WHERE (id = '{$id}')";
		return $wpdb->get_var($query);
	}
	
	private function setStatsNewData($id, $ip) {	//	Saves data to the table of new format
		global $wpdb, $table_prefix;
		$tableName	= $table_prefix . "vc_data";
		
		$dataExists	= TRUE;
		$data = $wpdb->get_var("SELECT data FROM " . $tableName . " WHERE (id = '" . $id . "')");
		if(!isset($data) || ("" == $data)) {
			$data = "00";
			$dataExists = FALSE;
		}
		
		$dArr = explode("/", $data);
		
		$views = sprintf("%02X", (hexdec(array_shift($dArr)) + 1) );	//	Increasing views counter
		
		$ip = VC_Format::ipToHEX($ip);
		if(FALSE === array_search($ip, $dArr)) {
			array_push($dArr, $ip);
		}
		
		array_unshift($dArr, $views);
		$data	= implode("/", $dArr);
		
		if($dataExists) {
			$wpdb->query("UPDATE " . $tableName . " SET data = '" . $data . "' WHERE (id='" . $id . "')");
		} else {
			$wpdb->query("INSERT INTO " . $tableName . "(id,data) VALUES('" . $id . "', '" . $data . "')");
		}
	}
	
	private function setStatsOldData($id, $ip) {	//	Saves data to the table of old format
		global $wpdb, $table_prefix;
		$tableName	= $table_prefix . "views_counter";			//	Usually equals to "wp_views_counter"
		
		$cnt = $wpdb->get_var("SELECT views FROM " . $tableName . " WHERE (id='$id' and ip='$ip')");
		
		if(isset($cnt))
		{
			$query = "UPDATE " . $tableName . " SET views=views+1 WHERE (id='$id' and ip='$ip')";	//	Increasing views counter
			$cnt++;
		}
		else
		{
			$query = "INSERT INTO " . $tableName . "(id,ip,views) VALUES('$id','$ip','1')";
			$cnt = 1;
		}
		$wpdb->query($query);
	}
	
	private function detectTableType() {			// 	Identifies the type of the table
		global $wpdb, $table_prefix;
		$tableName = $table_prefix . "vc_data";

		if( self::tableExists($tableName) ) {
			return NEW_TYPE_STATS;
		} else {
			return OLD_TYPE_STATS;
		}
	}
	
	public function tableExists($tableName) {	//	Checks whether specified table exists
		global $wpdb;
		
		if($wpdb->get_var("SHOW TABLES LIKE '$tableName'") != $tableName) return FALSE;
		return TRUE;
	}
	
	public function getStatsData($id) {				//	Retrieves all the information about post specified by id
		$newData = self::getStatsNewData($id);
		if(isset($newData)) return array(NEW_TYPE_STATS, $newData);

		$oldData = self::getStatsOldData($id);
		return array(OLD_TYPE_STATS, $oldData);
	}
	
	public function saveToDB($id, $ip) {			//	Saves data to the database
		$type = self::detectTableType();
		
		switch($type) {
			case OLD_TYPE_STATS:
				VC_System::logThis(1, "saveToDB():\tDetected old-type table. Starting migration for this entry");
				VC_MigrationMaster::migrate0_0To0_1();
				break;
			default:
				break;
		}

		self::convertToNewType($id);
		self::setStatsNewData($id, $ip);

	}
	
	private function convertToNewType($id) {
		
		VC_System::logThis(1, "convertToNewType():\tMigrating the post with ID " . $id);
		
		$oldData = self::getStatsOldData($id);
		if(!isset($oldData)) return false;
		$views 		= 0;
		$visitors =	array();

		foreach($oldData as $d) {
			$views += intval($d["views"]);
			array_push($visitors, VC_Format::ipToHEX($d["ip"]));
		}
		
		$res = sprintf("%02X", intval($views)) . "/" . implode("/", $visitors);
		
		VC_System::logThis(1, "convertToNewType():\tSubmitting entry '$res' to new-type table");
		
		global $table_prefix, $wpdb;
		$sql = "INSERT INTO " . $table_prefix . "vc_data(id, data) VALUES('" . $id . "', '" . $res . "')";
		VC_System::logThis(1, "Query:\n" . $sql);
	
		$wpdb->query($sql);
		
		VC_System::logThis(1, "convertToNewType():\tDeleting previous entry");
		
		$sql = "DELETE FROM " . $table_prefix . "views_counter WHERE (id='" . $id . "')";
		
		VC_System::logThis(1, "Query:\n" . $sql);
		
		$wpdb->query($sql);
		
		$sql = "SELECT COUNT(*) FROM " . $table_prefix . "views_counter WHERE 1";
		VC_System::logThis(1, "convertToNewType():\tQuery:\n" . $sql);
		$res = $wpdb->get_var($sql);
		
		VC_System::logThis(1, "Old-type table has $res entries");
		
		if($res == 0) {
			
			VC_System::logThis(1, "convertToNewType():\tDeleting old-type table ('" . $table_prefix . "views_counter')");
			$sql = "DROP TABLE IF EXISTS " . $table_prefix . "views_counter";
			VC_System::logThis(1, "Query:\n" . $sql);
			
			$wpdb->query($sql);
		}
		
		return true;
	}
}

class VC_System {
	function add_pages()		//	Adds different pages to Wordpress
	{
		add_options_page('Views Counter settings', 'Views Counter', 6, dirname(__FILE__) . "/options.php");
	}
	
	function add_stats_to_post_list($column_name,$id)		//	Adds stats to the list of posts at manage posts page
	{
		//	Checks if allowed
		if( (1 != get_settings("vc_show_in_posts_listing")) || ("vc_stats" != $column_name) ) return $post_columns;
		
		$stats = VC_Counter::getStats($id);
		
		if( (0 == $stats[0]) || (0 == $stats[1]) )
		{
			echo '<span title="Information not available. (No views at all?)">N/A</span>';
		}
		else
		{
			echo '<span style="text-align:center;display:block;" title="Viewed ' .
					 $stats[0] .
					 ' time' . ( 1 == $stats[0] ? "" : "s") .
					 ' by ' .
					 $stats[1] .
					 ' visitor' . ( 1 == $stats[1] ? "" : "s" ) .
					 '">' . implode($stats, "/") . '</span>';
		}
	}

	function add_header_to_post_list($post_columns)			//	Adds the header to new column at manage posts page
	{
		//	Checks if allowed
		if(1 != get_settings("vc_show_in_posts_listing")) return $post_columns;
		
		$post_columns['vc_stats'] = __('<a title="Navigate to VC\'s options page" href="' . get_bloginfo('wpurl') . '/wp-admin/options-general.php?page=vc/options.php#feature_11" target="_self">VC</a>');
		return $post_columns;
	}
	
	function logThis($status, $message) {
		$res = "[" . date("G:i:s, j.m.Y") . "]\t";
		switch($status) {		//	Creating different messages for different statuses
			case 3:						//	Error
				$res .=	"[ERROR]\t";
				break;
			case 2:						//	Warning
				$res .=	"[WARNING]\t";
				break;
			case 1:						//	Debug
				$commands = new VC_Commands();
				if(!$commands->assertEquals("-debug", "on")) return false;
				$res .= "[DEBUG]\t";
				break;
			default:					//	Info
				$res .=	"[INFO]\t";
		}
		$res .= $message . "\n";
		
		if($logFile = @fopen(dirname(__FILE__) . "/vc.log", "a")) {
			$result = fwrite($logFile, $res);
			fclose($logFile);
		}
	}
}

class VC_MigrationMaster {
	public function migrate0_0To0_1() {
		global $table_prefix, $wpdb;
		$table_name = $table_prefix . "vc_data";
		
		$query = <<<HERE
CREATE TABLE IF NOT EXISTS $table_name (
id INT(11) NOT NULL PRIMARY KEY,
data LONGTEXT NOT NULL
)
HERE;
		
		$result = mysql_query($query);
		if(FALSE === $result) {
			return FALSE;
		}

		return TRUE;
	}
}
?>